package jfconfig;

/**
 * Created : Apr 7, 2012
 *
 * @author pquiring
 */

// TODO : create dialog box to undo changes if things go bad

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import javaforce.*;
import javaforce.linux.Linux;
import static javaforce.linux.Linux.*;

public class DisplayPanel extends javax.swing.JPanel implements MouseMotionListener, MouseListener {

  /**
   * Creates new form DisplayPanel
   */
  public DisplayPanel() {
    initComponents();
    loadConfig();
    rescan();
//    restore = monitors;
    if (!new File("/usr/bin/nvidia-settings").exists()) {
      nvidia.setEnabled(false);
    }
  }

  /**
   * This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is always
   * regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    jToolBar1 = new javax.swing.JToolBar();
    back = new javax.swing.JButton();
    apply = new javax.swing.JButton();
    nvidia = new javax.swing.JButton();
    jLabel4 = new javax.swing.JLabel();
    resolution = new javax.swing.JComboBox();
    jPanel1 = new javax.swing.JPanel();
    view = new javax.swing.JScrollPane();
    layout = new javax.swing.JPanel();
    mirror = new javax.swing.JCheckBox();
    jLabel7 = new javax.swing.JLabel();
    rotation = new javax.swing.JComboBox();
    jLabel1 = new javax.swing.JLabel();
    displays = new javax.swing.JComboBox();

    jToolBar1.setFloatable(false);
    jToolBar1.setRollover(true);

    back.setText("< Back");
    back.setFocusable(false);
    back.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    back.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    back.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        backActionPerformed(evt);
      }
    });
    jToolBar1.add(back);

    apply.setText("Apply");
    apply.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        applyActionPerformed(evt);
      }
    });
    jToolBar1.add(apply);

    nvidia.setText("nVidia Settings");
    nvidia.setFocusable(false);
    nvidia.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    nvidia.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    nvidia.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        nvidiaActionPerformed(evt);
      }
    });
    jToolBar1.add(nvidia);

    jLabel4.setText("Resolution");

    resolution.addItemListener(new java.awt.event.ItemListener() {
      public void itemStateChanged(java.awt.event.ItemEvent evt) {
        resolutionItemStateChanged(evt);
      }
    });

    jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Position"));

    layout.setLayout(null);
    view.setViewportView(layout);

    javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
    jPanel1.setLayout(jPanel1Layout);
    jPanel1Layout.setHorizontalGroup(
      jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel1Layout.createSequentialGroup()
        .addContainerGap()
        .addComponent(view)
        .addContainerGap())
    );
    jPanel1Layout.setVerticalGroup(
      jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(jPanel1Layout.createSequentialGroup()
        .addComponent(view, javax.swing.GroupLayout.DEFAULT_SIZE, 341, Short.MAX_VALUE)
        .addContainerGap())
    );

    mirror.setText("Mirror Display");
    mirror.addItemListener(new java.awt.event.ItemListener() {
      public void itemStateChanged(java.awt.event.ItemEvent evt) {
        mirrorItemStateChanged(evt);
      }
    });

    jLabel7.setText("Rotation");

    rotation.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Normal", "ClockWise", "CounterClockWise", "Inverted" }));
    rotation.addItemListener(new java.awt.event.ItemListener() {
      public void itemStateChanged(java.awt.event.ItemEvent evt) {
        rotationItemStateChanged(evt);
      }
    });

    jLabel1.setText("Display");

    displays.addItemListener(new java.awt.event.ItemListener() {
      public void itemStateChanged(java.awt.event.ItemEvent evt) {
        displaysItemStateChanged(evt);
      }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
    this.setLayout(layout);
    layout.setHorizontalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addComponent(jToolBar1, javax.swing.GroupLayout.DEFAULT_SIZE, 521, Short.MAX_VALUE)
      .addGroup(layout.createSequentialGroup()
        .addContainerGap()
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
          .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
          .addGroup(layout.createSequentialGroup()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addComponent(jLabel4)
              .addComponent(jLabel7)
              .addComponent(jLabel1))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(layout.createSequentialGroup()
                .addComponent(displays, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(mirror))
              .addComponent(resolution, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
              .addComponent(rotation, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
        .addContainerGap())
    );
    layout.setVerticalGroup(
      layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
      .addGroup(layout.createSequentialGroup()
        .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel1)
          .addComponent(displays, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(mirror))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(jLabel4)
          .addComponent(resolution, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
          .addComponent(rotation, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
          .addComponent(jLabel7))
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
        .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .addContainerGap())
    );
  }// </editor-fold>//GEN-END:initComponents

  private void backActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backActionPerformed
    ConfigApp.setPanel(new MainPanel());
  }//GEN-LAST:event_backActionPerformed

  private void applyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyActionPerformed
    apply();
  }//GEN-LAST:event_applyActionPerformed

  private void nvidiaActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nvidiaActionPerformed
    try {
      Runtime.getRuntime().exec("nvidia-settings");
    } catch (Exception e) {
    }
  }//GEN-LAST:event_nvidiaActionPerformed

  private void resolutionItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_resolutionItemStateChanged
  }//GEN-LAST:event_resolutionItemStateChanged

  private void mirrorItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_mirrorItemStateChanged
    if (selectedMonitor == monitors[0]) return;  //can not mirror primary monitor
    boolean state = mirror.isSelected();
    boolean oldState = selectedMonitor.mirror;
    selectedMonitor.mirror = state;
    if (state) {
      selectedMonitor.relpos = P_SAME;
      selectedMonitor.relName = monitors[0].name;
    } else {
      //set an invalid position (x11_rr_arrangeMonitors() will place it somewhere)
      selectedMonitor.relpos = P_NONE;
      selectedMonitor.relName = "";
      Linux.x11_rr_arrangeMonitors(monitors);
    }
    if (state != oldState) addJMonitors();  //removing or adding a monitor
  }//GEN-LAST:event_mirrorItemStateChanged

  private void rotationItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_rotationItemStateChanged
  }//GEN-LAST:event_rotationItemStateChanged

  private void displaysItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_displaysItemStateChanged
    int idx = displays.getSelectedIndex();
    if (idx == -1) return;
    if (selectedMonitor != monitors[idx]) selectMonitor(monitors[idx]);
  }//GEN-LAST:event_displaysItemStateChanged

  // Variables declaration - do not modify//GEN-BEGIN:variables
  private javax.swing.JButton apply;
  private javax.swing.JButton back;
  private javax.swing.JComboBox displays;
  private javax.swing.JLabel jLabel1;
  private javax.swing.JLabel jLabel4;
  private javax.swing.JLabel jLabel7;
  private javax.swing.JPanel jPanel1;
  private javax.swing.JToolBar jToolBar1;
  private javax.swing.JPanel layout;
  private javax.swing.JCheckBox mirror;
  private javax.swing.JButton nvidia;
  private javax.swing.JComboBox resolution;
  private javax.swing.JComboBox rotation;
  private javax.swing.JScrollPane view;
  // End of variables declaration//GEN-END:variables

/*
  private final int P_NONE = 0;
  private final int P_LEFT = 1;
  private final int P_ABOVE = 2;
  private final int P_RIGHT = 3;
  private final int P_BELOW = 4;
  private final int P_SAME = 5;

  private final int R_NORMAL = 0;
  private final int R_RIGHT = 1;  //CW
  private final int R_LEFT = 2;  //CCW
  private final int R_INVERTED = 3;
*/

  private Monitor monitors[]; //, restore[];

  private static Monitor selectedMonitor = null;

  private static class JMonitor extends JLabel {
    public Monitor monitor;
    public int x,y;  //position on screen
    public boolean layout;
    public JMonitor(String label) {
      super(label);
    }
    public void paintComponent(Graphics g) {
      if (selectedMonitor == monitor) {
        setBackground(Color.blue);
      } else {
        setBackground(Color.gray);
      }
      super.paintComponent(g);
    }
  }

  private void loadConfig() {
    monitors = Linux.x11_rr_load_user();
  }

  private void saveConfig() {
    Linux.x11_rr_save_user(monitors);
  }

  private void layoutMonitors() {
    int x = BOXSIZE, y = BOXSIZE;
    int nx = 0, ny = 0;  //max negative x,y
    JMonitor list[] = getJMonitors();
    for(int j=0;j<list.length;j++) {
      list[j].layout = false;
    }
    boolean redo;
    int cnt = 0;
    do {
      redo = false;
      cnt++;
      if (cnt == 100) break;
      for(int m=0;m<monitors.length;m++) {
        Monitor monitor = monitors[m];
        if (monitor.mirror) continue;
        JMonitor jmonitor = null, adjacent = null;
        for(int j=0;j<list.length;j++) {
          JMonitor test = list[j];
          if (test.monitor.name.equals(monitor.name)) jmonitor = test;
          if (test.monitor.name.equals(monitor.relName)) adjacent = test;
        }
        if (jmonitor == null) continue;
        if (jmonitor.layout) continue;
        if (m == 0) {
          monitor.relName = "";
          monitor.relpos = P_NONE;
        }
        if (monitor.relpos != P_NONE && adjacent != null && !adjacent.layout) {redo = true; continue;}
        switch (monitor.relpos) {
          case P_NONE:
          case P_SAME:
            jmonitor.x = x;
            jmonitor.y = y;
            break;
          case P_LEFT:
            jmonitor.x = adjacent.x - BOXSIZE;
            jmonitor.y = adjacent.y;
            break;
          case P_RIGHT:
            jmonitor.x = adjacent.x + BOXSIZE;
            jmonitor.y = adjacent.y;
            break;
          case P_ABOVE:
            jmonitor.x = adjacent.x;
            jmonitor.y = adjacent.y - BOXSIZE;
            break;
          case P_BELOW:
            jmonitor.x = adjacent.x;
            jmonitor.y = adjacent.y + BOXSIZE;
            break;
        }
        jmonitor.setBounds(jmonitor.x,jmonitor.y,BOXSIZE,BOXSIZE);
        if (jmonitor.x < nx) nx = jmonitor.x;
        if (jmonitor.y < ny) ny = jmonitor.y;
        jmonitor.layout = true;
      }
    } while (redo);
    if (nx < 0 || ny < 0) {
      //need to shift everything
      for(int j=0;j<list.length;j++) {
        JMonitor jmonitor = list[j];
        jmonitor.x -= nx;
        jmonitor.y -= ny;
        jmonitor.setBounds(jmonitor.x,jmonitor.y,BOXSIZE,BOXSIZE);
      }
    }
  }

  private void moveMonitor(JMonitor jmonitor) {
    if (jmonitor.monitor == monitors[0]) return;
    int x = jmonitor.getX();
    int y = jmonitor.getY();
    int bestlen = 0x7fffffff;
    JMonitor adjacent = null;
    int pos = 0;
    int dx,dy,len;
    Monitor monitor = jmonitor.monitor;
    JMonitor list[] = getJMonitors();
    for(int j=0;j<list.length;j++) {
      JMonitor test = list[j];
      if (test == jmonitor) continue;
      dx = (test.x - BOXSIZE) - x;
      dy = test.y - y;
      len = (int)Math.sqrt(dx * dx + dy * dy);
      if (len < bestlen) {
        adjacent = test;
        pos = P_LEFT;
        bestlen = len;
      }
      dx = (test.x + BOXSIZE) - x;
      dy = test.y - y;
      len = (int)Math.sqrt(dx * dx + dy * dy);
      if (len < bestlen) {
        adjacent = test;
        pos = P_RIGHT;
        bestlen = len;
      }
      dx = test.x - x;
      dy = (test.y + BOXSIZE) - y;
      len = (int)Math.sqrt(dx * dx + dy * dy);
      if (len < bestlen) {
        adjacent = test;
        pos = P_BELOW;
        bestlen = len;
      }
      dx = test.x - x;
      dy = (test.y - BOXSIZE) - y;
      len = (int)Math.sqrt(dx * dx + dy * dy);
      if (len < bestlen) {
        adjacent = test;
        pos = P_ABOVE;
        bestlen = len;
      }
    }
    if (adjacent == null) return;
    if (adjacent.monitor.name.equals(jmonitor.monitor.name) && adjacent.monitor.relName.equals(jmonitor.monitor.name)) {
      adjacent.monitor.relpos = monitor.relpos;
      adjacent.monitor.relName = monitor.relName;
    }
    jmonitor.monitor.relpos = pos;
    jmonitor.monitor.relName = adjacent.monitor.name;
  }

  private void removeMonitors() {
    layout.removeAll();
  }

  private JMonitor makeJMonitor(Monitor monitor) {
    JMonitor jmonitor = new JMonitor(monitor.name);
    jmonitor.monitor = monitor;
    jmonitor.addMouseListener(this);
    jmonitor.addMouseMotionListener(this);
    jmonitor.setBorder(javax.swing.BorderFactory.createEtchedBorder());
    jmonitor.setHorizontalAlignment(SwingConstants.CENTER);
    return jmonitor;
  }

  private final int BOXSIZE = 100;  //monitor width/height

  private void addJMonitors() {
    removeMonitors();
    for(int m=0;m<monitors.length;m++) {
      Monitor monitor = monitors[m];
//      if (monitor.mirror) continue;
      JMonitor jmonitor = makeJMonitor(monitor);
      layout.add(jmonitor);
    }
    layoutMonitors();
    layout.repaint();
  }

  private boolean apply_rr() {
    Linux.x11_rr_set(monitors);
    return true;
  }

  private static String quote(String str) {
    return "\"" + str + "\"";
  }

  private void apply() {
    saveMonitor();
    saveConfig();
    if (!apply_rr()) {
      JFAWT.showError("Error", "Failed to apply configuration");
      return;
    }
    ConfigApp.jbusClient.call("org.jflinux.jfsystemmgr", "broadcastVideoChanged", quote("jconfig"));
  }

  private void saveMonitor() {
    if (selectedMonitor == null) return;
    //update values
    selectedMonitor.res = (String)resolution.getSelectedItem();
    selectedMonitor.rotate = rotation.getSelectedIndex();
//    selectedMonitor.mirror = mirror.isSelected();  //done in mirror itemStateChanged()
  }

  private void selectMonitor(Monitor monitor) {
    if (selectedMonitor != null) {
      saveMonitor();
    }
    Screen screen;
    Port port;
    resolution.removeAllItems();
    int idx = -1;
    boolean done = false;
    for(int b=0;b<screens.size();b++) {
      screen = screens.get(b);
      for(int c=0;c<screen.ports.size();c++) {
        port = screen.ports.get(c);
        if (monitor.name.equals(port.name)) {
          //update resolution options
          for(int d=0;d<port.sizes.size();d++) {
            Size size = port.sizes.get(d);
            resolution.addItem(size.size);
            if (monitor.res.equals(size.size)) {
              idx = d;
            }
          }
          done = true;
          break;
        }
        if (done) break;
      }
      if (done) break;
    }
    if (idx != -1) resolution.setSelectedIndex(idx);
    rotation.setSelectedIndex(monitor.rotate);
    idx = -1;
    for(int a=0;a<monitors.length;a++) {
      if (monitors[a] == monitor) {
        idx = a;
        break;
      }
    }
    JMonitor list[] = getJMonitors();
    for(int a=0;a<list.length;a++) {
      if (list[a].monitor == monitor) {
        selectedMonitor = monitor;
        break;
      }
    }
    mirror.setSelected(monitor.mirror);
    mirror.setEnabled(idx != 0);
    displays.setSelectedIndex(idx);
    layout.repaint();
  }

  private boolean drag;
  private int ix, iy;  //init pos
  private int sx, sy;  //start x,y

  public void mouseClicked(MouseEvent me) {
  }

  public void mousePressed(MouseEvent me) {
    if (layout.getComponentCount() == 1) return;  //nothing to move to
    JMonitor jmonitor = (JMonitor)me.getSource();
    ix = jmonitor.getX();
    iy = jmonitor.getY();
    sx = me.getXOnScreen();
    sy = me.getYOnScreen();
    drag = true;
//    System.out.println("pressed:" + sx + "," + sy);
    selectMonitor(jmonitor.monitor);
 }

  public void mouseReleased(MouseEvent me) {
    if (drag) {
      JMonitor jmonitor = (JMonitor)me.getSource();
      moveMonitor(jmonitor);
      layoutMonitors();
      layout.repaint();
      drag = false;
    }
  }

  public void mouseEntered(MouseEvent me) {
  }

  public void mouseExited(MouseEvent me) {
  }

  public void mouseDragged(MouseEvent me) {
    if (!drag) return;
    JMonitor jmonitor = (JMonitor)me.getSource();
    int x = me.getXOnScreen();
    int y = me.getYOnScreen();
//    System.out.println("dragged:" + x + "," + y);
    jmonitor.setLocation(ix + (x - sx), iy + (y - sy));
  }

  public void mouseMoved(MouseEvent me) {
  }

  public void rescan() {
    monitors = Linux.x11_rr_get_setup(monitors);
    monitors[0].relName = "";
    monitors[0].relpos = P_NONE;
    displays.removeAllItems();
    for(int a=0;a<monitors.length;a++) {
      displays.addItem(monitors[a].name);
    }
    addJMonitors();
    selectMonitor(monitors[0]);
  }

  private JMonitor[] getJMonitors() {
    Component list[] = layout.getComponents();
    JMonitor mlist[] = new JMonitor[list.length];
    for(int a=0;a<list.length;a++) {
      mlist[a] = (JMonitor)list[a];
    }
    return mlist;
  }
}
